/*!
 *
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 *
 * Copyright (c) 2002-2018 Hitachi Vantara. All rights reserved.
 *
 */

package org.pentaho.platform.config;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jaxen.JaxenException;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.dom4j.Dom4jXPath;
import org.pentaho.platform.config.messages.Messages;
import org.pentaho.platform.util.xml.XMLParserFactoryProducer;
import org.pentaho.platform.util.xml.dom4j.XmlDom4JHelper;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;

public class PentahoObjectsConfig {
  private static final String DEFAULT_NAMESPACE = "http://www.springframework.org/schema/beans"; //$NON-NLS-1$
  private static final String DEFAULT = "default"; //$NON-NLS-1$
  private static final String ROOT = "beans"; //$NON-NLS-1$
  private static final String ROOT_ELEMENT = DEFAULT + ":beans"; //$NON-NLS-1$
  private static final String BEAN_ELEMENT = DEFAULT + ":bean"; //$NON-NLS-1$
  private static final String ID_ATTRIBUTE = "id"; //$NON-NLS-1$
  private static final String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$
  private static final String SCOPE_ATTRIBUTE = "scope"; //$NON-NLS-1$
  private static final String BEAN_ID_XPATH = ROOT_ELEMENT + "/" + BEAN_ELEMENT + "[@" + ID_ATTRIBUTE + "=\"{0}\"]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
  public static final String SOLUTION_ENGINE_ID = "ISolutionEngine"; //$NON-NLS-1$
  public static final String CONTENT_REPOSITORY_ID = "IContentRepository"; //$NON-NLS-1$ 
  public static final String RUNTIME_REPOSITORY_ID = "IRuntimeRepository"; //$NON-NLS-1$
  public static final String AUDIT_FILE_ENTRY_ID = "IAuditEntry"; //$NON-NLS-1$
  public static final String UI_TEMPLATER_ID = "IUITemplater"; //$NON-NLS-1$
  public static final String USER_FILES_COMPONENT_ID = "IUserFilesComponent"; //$NON-NLS-1$
  public static final String BACKGROUND_EXECUTION_HELPER_ID = "IBackgroundExecution"; //$NON-NLS-1$
  public static final String SUBSCRIPTION_REPOSITORY_ID = "ISubscriptionRepository"; //$NON-NLS-1$ 
  public static final String SUBSCRIPTION_SCHEDULER_ID = "ISubscriptionScheduler"; //$NON-NLS-1$
  public static final String USER_SETTINGS_SERVICE_ID = "IUserSettingService"; //$NON-NLS-1$
  public static final String FILE_OUTPUT_HANDLER_ID = "file"; //$NON-NLS-1$
  public static final String CONTENT_REPOSITORY_OUTPUT_HANDLER_ID = "contentrepo"; //$NON-NLS-1$
  public static final String ACL_PUBLISHER_ID = "IAclPublisher"; //$NON-NLS-1$
  public static final String ACL_VOTER_ID = "IAclVoter"; //$NON-NLS-1$
  public static final String VERSION_HELPER_ID = "IVersionHelper"; //$NON-NLS-1$
  public static final String CACHE_MGR_ID = "ICacheManager"; //$NON-NLS-1$
  public static final String SCHEDULER_ID = "IScheduler"; //$NON-NLS-1$
  public static final String CONDITONAL_EXECUTION_ID = "IConditionalExecution"; //$NON-NLS-1$
  public static final String MSG_FORMATTER_ID = "IMessageFormatter"; //$NON-NLS-1$
  public static final String DATA_SOURCE_SERVICE_ID = "IDatasourceService"; //$NON-NLS-1$
  public static final String PASSWORD_SERVICE_ID = "IPasswordService"; //$NON-NLS-1$
  public static final String DATA_SOURCE_ID = "IDatasource"; //$NON-NLS-1$
  public static final String DATA_SOURCE_MGMT_SERVICE_ID = "IDatasourceMgmtService"; //$NON-NLS-1$
  public static final String PROTOTYPE = "prototype"; //$NON-NLS-1$
  public static final String SESSION = "session"; //$NON-NLS-1$
  public static final String SINGLETON = "singleton"; //$NON-NLS-1$

  public enum ScopeType {
    undefined, prototype, session, singleton
  }

  Document document;

  public PentahoObjectsConfig( File pentahoXmlFile ) throws IOException, DocumentException {
    setDocument( XmlDom4JHelper.getDocFromFile( pentahoXmlFile, null ) );
  }

  public PentahoObjectsConfig( String xml ) throws DocumentException {
    SAXReader reader = XMLParserFactoryProducer.getSAXReader( null );
    reader.setValidation( false );
    setDocument( reader.read( new ByteArrayInputStream( xml.getBytes() ) ) );
  }

  public void setDocument( Document doc ) throws DocumentException {
    Element rootElement = doc.getRootElement();
    if ( ( rootElement != null ) && !doc.getRootElement().getName().equals( ROOT ) ) {
      throw new DocumentException( Messages.getInstance().getErrorString(
          "PentahoObjectsConfig.ERROR_0001_INVALID_ROOT_ELEMENT" ) ); //$NON-NLS-1$
    }
    document = doc;
  }

  public PentahoObjectsConfig() {
    document = DocumentHelper.createDocument();
    document.addElement( ROOT_ELEMENT );
  }

  public String getSolutionEngine() {
    return getObjectClassName( SOLUTION_ENGINE_ID );
  }

  public void setSolutionEngine( String solutionEngine, ScopeType scope ) {
    updateObject( SOLUTION_ENGINE_ID, new ObjectDescriptor( solutionEngine, scope ) );
  }

  public String getContentRepository() {
    return getObjectClassName( CONTENT_REPOSITORY_ID );
  }

  public void setContentRepository( String contentRepository, ScopeType scope ) {
    updateObject( CONTENT_REPOSITORY_ID, new ObjectDescriptor( contentRepository, scope ) );
  }

  public String getRuntimeRepository() {
    return getObjectClassName( RUNTIME_REPOSITORY_ID );
  }

  public void setRuntimeRepository( String runtimeRepository, ScopeType scope ) {
    updateObject( RUNTIME_REPOSITORY_ID, new ObjectDescriptor( runtimeRepository, scope ) );
  }

  public String getAuditFileEntry() {
    return getObjectClassName( AUDIT_FILE_ENTRY_ID );
  }

  public void setAuditFileEntry( String auditFileEntry, ScopeType scope ) {
    updateObject( AUDIT_FILE_ENTRY_ID, new ObjectDescriptor( auditFileEntry, scope ) );
  }

  public String getUiTemplater() {
    return getObjectClassName( UI_TEMPLATER_ID );
  }

  public void setUiTemplater( String uiTemplater, ScopeType scope ) {
    updateObject( UI_TEMPLATER_ID, new ObjectDescriptor( uiTemplater, scope ) );
  }

  public String getUserFilesComponent() {
    return getObjectClassName( USER_FILES_COMPONENT_ID );
  }

  public void setUserFilesComponent( String userFilesComponent, ScopeType scope ) {
    updateObject( USER_FILES_COMPONENT_ID, new ObjectDescriptor( userFilesComponent, scope ) );
  }

  public String getBackgroundExecutionHelper() {
    return getObjectClassName( BACKGROUND_EXECUTION_HELPER_ID );
  }

  public void setBackgroundExecutionHelper( String backgroundExecutionHelper, ScopeType scope ) {
    updateObject( BACKGROUND_EXECUTION_HELPER_ID, new ObjectDescriptor( backgroundExecutionHelper, scope ) );
  }

  public String getSubscriptionRepository() {
    return getObjectClassName( SUBSCRIPTION_REPOSITORY_ID );
  }

  public void setSubscriptionRepository( String subscriptionRepository, ScopeType scope ) {
    updateObject( SUBSCRIPTION_REPOSITORY_ID, new ObjectDescriptor( subscriptionRepository, scope ) );
  }

  public String getSubscriptionScheduler() {
    return getObjectClassName( SUBSCRIPTION_SCHEDULER_ID );
  }

  public void setSubscriptionScheduler( String subscriptionScheduler, ScopeType scope ) {
    updateObject( SUBSCRIPTION_SCHEDULER_ID, new ObjectDescriptor( subscriptionScheduler, scope ) );
  }

  public String getUserSettingsService() {
    return getObjectClassName( USER_SETTINGS_SERVICE_ID );
  }

  public void setUserSettingsService( String userSettingsService, ScopeType scope ) {
    updateObject( USER_SETTINGS_SERVICE_ID, new ObjectDescriptor( userSettingsService, scope ) );
  }

  public String getFileOutputHandler() {
    return getObjectClassName( FILE_OUTPUT_HANDLER_ID );
  }

  public void setFileOutputHandler( String fileOutputHandler, ScopeType scope ) {
    updateObject( FILE_OUTPUT_HANDLER_ID, new ObjectDescriptor( fileOutputHandler, scope ) );
  }

  public String getContentRepositoryOutputHandler() {
    return getObjectClassName( CONTENT_REPOSITORY_OUTPUT_HANDLER_ID );
  }

  public void setContentRepositoryOutputHandler( String contentRepositoryOutputHandler, ScopeType scope ) {
    updateObject( CONTENT_REPOSITORY_OUTPUT_HANDLER_ID, new ObjectDescriptor( contentRepositoryOutputHandler, scope ) );
  }

  public String getAclPublisher() {
    return getObjectClassName( ACL_PUBLISHER_ID );
  }

  public void setAclPublisher( String aclPublisher, ScopeType scope ) {
    updateObject( ACL_PUBLISHER_ID, new ObjectDescriptor( aclPublisher, scope ) );
  }

  public String getAclVoter() {
    return getObjectClassName( ACL_VOTER_ID );
  }

  public void setAclVoter( String aclVoter, ScopeType scope ) {
    updateObject( ACL_VOTER_ID, new ObjectDescriptor( aclVoter, scope ) );
  }

  public String getVersionHelper() {
    return getObjectClassName( VERSION_HELPER_ID );
  }

  public void setVersionHelper( String versionHelper, ScopeType scope ) {
    updateObject( VERSION_HELPER_ID, new ObjectDescriptor( versionHelper, scope ) );
  }

  public String getCacheManager() {
    return getObjectClassName( CACHE_MGR_ID );
  }

  public void setCacheManager( String cacheManager, ScopeType scope ) {
    updateObject( CACHE_MGR_ID, new ObjectDescriptor( cacheManager, scope ) );
  }

  public String getScheduler() {
    return getObjectClassName( SCHEDULER_ID );
  }

  public void setScheduler( String scheduler, ScopeType scope ) {
    updateObject( SCHEDULER_ID, new ObjectDescriptor( scheduler, scope ) );
  }

  public String getConditionalExecution() {
    return getObjectClassName( CONDITONAL_EXECUTION_ID );
  }

  public void setConditionalExecution( String conditionalExecution, ScopeType scope ) {
    updateObject( CONDITONAL_EXECUTION_ID, new ObjectDescriptor( conditionalExecution, scope ) );
  }

  public String getMessageFormatter() {
    return getObjectClassName( MSG_FORMATTER_ID );
  }

  public void setMessageFormatter( String messageFormatter, ScopeType scope ) {
    updateObject( MSG_FORMATTER_ID, new ObjectDescriptor( messageFormatter, scope ) );
  }

  public String getDataSourceService() {
    return getObjectClassName( DATA_SOURCE_SERVICE_ID );
  }

  public void setDataSourceService( String dataSourceService, ScopeType scope ) {
    updateObject( DATA_SOURCE_SERVICE_ID, new ObjectDescriptor( dataSourceService, scope ) );
  }

  public String getPasswordService() {
    return getObjectClassName( PASSWORD_SERVICE_ID );
  }

  public void setPasswordService( String passwordService, ScopeType scope ) {
    updateObject( PASSWORD_SERVICE_ID, new ObjectDescriptor( passwordService, scope ) );
  }

  public String getDataSource() {
    return getObjectClassName( DATA_SOURCE_ID );
  }

  public void setDataSource( String dataSource, ScopeType scope ) {
    updateObject( DATA_SOURCE_ID, new ObjectDescriptor( dataSource, scope ) );
  }

  public String getDataSourcMgmtService() {
    return getObjectClassName( DATA_SOURCE_MGMT_SERVICE_ID );
  }

  public void setDataSourcMgmtService( String dataSourcMgmtService, ScopeType scope ) {
    updateObject( DATA_SOURCE_MGMT_SERVICE_ID, new ObjectDescriptor( dataSourcMgmtService, scope ) );
  }

  protected Element getObjectBeanElement( String objectId ) {
    try {
      String xPath = MessageFormat.format( BEAN_ID_XPATH, objectId );
      HashMap<String, String> map = new HashMap<String, String>();
      map.put( "default", DEFAULT_NAMESPACE ); //$NON-NLS-1$
      Dom4jXPath xpath = new Dom4jXPath( xPath );
      xpath.setNamespaceContext( new SimpleNamespaceContext( map ) );
      Element element = (Element) xpath.selectSingleNode( document );
      return element;
    } catch ( JaxenException jex ) {
      return null;
    }
  }

  public void setObject( String objectId, ObjectDescriptor objectDescriptor ) {
    Element beanDefinitionElement = getObjectBeanElement( objectId );
    if ( objectDescriptor == null ) {
      if ( beanDefinitionElement != null ) {
        beanDefinitionElement.detach();
      }
    } else {
      if ( beanDefinitionElement == null ) {
        beanDefinitionElement = document.addElement( BEAN_ELEMENT );
        beanDefinitionElement.addAttribute( ID_ATTRIBUTE, objectId );
        beanDefinitionElement.addAttribute( CLASS_ATTRIBUTE, objectDescriptor.getClassName() );
        String scope = objectDescriptor.getScope().name();
        // If scope is not specified then use prototype scope as default
        if ( scope == null || scope.length() > 0 ) {
          scope = ScopeType.prototype.name();
        }
        beanDefinitionElement.addAttribute( SCOPE_ATTRIBUTE, scope );
      }
    }
  }

  public void updateObject( String objectId, ObjectDescriptor objectDescriptor ) {
    Element beanDefinitionElement = getObjectBeanElement( objectId );
    if ( beanDefinitionElement != null ) {
      String className = objectDescriptor.getClassName();
      ScopeType scope = objectDescriptor.getScope();
      if ( className != null && className.length() > 0 ) {
        beanDefinitionElement.addAttribute( CLASS_ATTRIBUTE, className );
      }
      if ( scope != null && scope.name() != null ) {
        beanDefinitionElement.addAttribute( SCOPE_ATTRIBUTE, scope.name() );
      }
    } else {
      setObject( objectId, objectDescriptor );
    }
  }

  public String getObjectClassName( String objectId ) {
    try {
      String xPath = MessageFormat.format( BEAN_ID_XPATH, objectId );
      HashMap<String, String> map = new HashMap<String, String>();
      map.put( "default", DEFAULT_NAMESPACE ); //$NON-NLS-1$
      Dom4jXPath xpath = new Dom4jXPath( xPath );
      xpath.setNamespaceContext( new SimpleNamespaceContext( map ) );
      Element element = (Element) xpath.selectSingleNode( document );
      return element.attributeValue( CLASS_ATTRIBUTE );
    } catch ( JaxenException jex ) {
      return null;
    }
  }

  public String getObjectScope( String objectId ) {
    try {
      HashMap<String, String> map = new HashMap<String, String>();
      map.put( "default", DEFAULT_NAMESPACE ); //$NON-NLS-1$
      Dom4jXPath xpath = new Dom4jXPath( BEAN_ID_XPATH );
      xpath.setNamespaceContext( new SimpleNamespaceContext( map ) );
      Element element = (Element) xpath.selectSingleNode( document );
      return element.attributeValue( SCOPE_ATTRIBUTE );
    } catch ( JaxenException jex ) {
      return null;
    }

  }

  public ObjectDescriptor getObject( String objectId ) {
    String xPathClass = MessageFormat.format( BEAN_ID_XPATH, objectId );
    Element elementClass = (Element) document.selectSingleNode( xPathClass );
    String xPathScope = MessageFormat.format( BEAN_ID_XPATH, objectId );
    Element elementScope = (Element) document.selectSingleNode( xPathScope );
    return new ObjectDescriptor( elementClass.attributeValue( CLASS_ATTRIBUTE ), stringToScopeType( elementScope
        .attributeValue( SCOPE_ATTRIBUTE ) ) );
  }

  public Document getDocument() {
    return document;
  }

  protected class ObjectDescriptor {
    private String className;
    private ScopeType scope;

    public ObjectDescriptor() {

    }

    public ObjectDescriptor( String className, ScopeType scope ) {
      this.className = className;
      this.scope = scope;
    }

    public String getClassName() {
      return this.className;
    }

    public ScopeType getScope() {
      return this.scope;
    }
  }

  public ScopeType stringToScopeType( String scopeTypeString ) {
    if ( scopeTypeString == null ) {
      return ScopeType.undefined;
    }
    if ( scopeTypeString.equals( PROTOTYPE ) ) {
      return ScopeType.prototype;
    }
    if ( scopeTypeString.equals( SESSION ) ) {
      return ScopeType.session;
    }
    if ( scopeTypeString.equals( SINGLETON ) ) {
      return ScopeType.singleton;
    }
    return ScopeType.undefined;
  }
}
